﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using PMatch.Sampling;
using PMatch.Formats.SequenceSample;

namespace seqsampler
{
	/// <summary>
	/// Modo de ejecucion
	/// </summary>
	public enum Mode
	{
		Collect,
		FindBestWindow,
		MinSubstringLength
	}

	/// <summary>
	/// Configuracion para ejecutar el programa
	/// </summary>
	public class ExecSettings
	{
		public Mode Mode { get; set; }

		public int? PrefixLength { get; set; }
		public int? SufixLength { get; set; }

		public int? MinPrefixLength { get; set; }
		public int? MinSufixLength { get; set; }

		public int? MaxPrefixLength { get; set; }
		public int? MaxSufixLength { get; set; }

		public int SiteIndex { get; set; }
		public string InputFile { get; set; }
		public string OutputFile { get; set; }
		public bool BreakOnCollision { get; set; }
		public int? MaxCollidingOffset { get; set; }
		public int? MaxNegativeCollidingOffset { get; set; }
		public int? MaxPositiveCollidingOffset { get; set; }
	}

	class Program
	{

		static void Main(string[] args)
		{
			ExecSettings settings;
			ParseArguments(args, out settings);
			try
			{
				Execute(settings);
			}
			catch (Exception ex)
			{
				var eex = ex;
				do
				{
					Console.Error.WriteLine(ex.Message + "\n");
				} while ((eex = eex.InnerException) != null);
			}
		}

		/// <summary>
		/// Ejecuta la aplicacion con los parametros procesados
		/// </summary>		
		private static void Execute(ExecSettings settings)
		{
			FindBestWindowCallback callback = status =>
			{
				// Maximum Collision Offset
				var hasCN = status.CollisionsWithNegativeSet.Count > 0;
				var hasCP = status.CollisionsWithPositiveSet.Count > 0;
				int? maxCON = null;
				int? maxCOP = null;

				if (hasCN) maxCON = status.CollisionsWithNegativeSet.Max(x => Math.Abs(x.SampleColliding.Site - x.SampleOriginal.Site));
				if (hasCP) maxCOP = status.CollisionsWithPositiveSet.Max(x => Math.Abs(x.SampleColliding.Site - x.SampleOriginal.Site));

				// Notificacion de progreso
				Console.WriteLine("Progress {0:P1} [{4}:{5}] | (cn: {1}) + (cp: {2}) = {3} | MCON = {6} | MCOP = {7}",
					(decimal)status.Count / (decimal)status.Total,
					status.CollisionsWithNegativeSet.Count, status.CollisionsWithPositiveSet.Count,
					status.CollisionsWithNegativeSet.Count + status.CollisionsWithPositiveSet.Count,
					status.PrefixLength, status.SufixLength,
					maxCON, maxCOP);

				if (settings.MaxNegativeCollidingOffset.HasValue)
				{
					status.Stop = status.TakeCurrent = maxCON <= settings.MaxNegativeCollidingOffset.Value;
				}
				else if (settings.MaxPositiveCollidingOffset.HasValue)
				{
					status.Stop = status.TakeCurrent = maxCOP <= settings.MaxPositiveCollidingOffset.Value;
				}
				else if (settings.MaxCollidingOffset.HasValue)
				{
					var total = 0;
					total += maxCON.HasValue ? maxCON.Value : 0;
					total += maxCOP.HasValue ? maxCOP.Value : 0;
					status.Stop = status.TakeCurrent = total <= settings.MaxCollidingOffset.Value;
				}
			};

			var rs = PMatch.Formats.sPMatch.sequences.LoadFromFile(settings.InputFile);
			Console.WriteLine("Read: {0} sequences", rs.sequence.Count);
			bool breakOnCollision = settings.BreakOnCollision && !(settings.MaxNegativeCollidingOffset.HasValue || settings.MaxPositiveCollidingOffset.HasValue);
			if (settings.Mode == Mode.MinSubstringLength)
			{
				var s2 = WindowOptimizer.FindMinSubstringSize(rs.sequence, settings.PrefixLength.Value, settings.SufixLength.Value);
			}
			else if (settings.Mode == Mode.FindBestWindow)
			{
				var res = WindowOptimizer.FindBestWindow(rs.sequence, settings.MinPrefixLength.Value, settings.MaxPrefixLength.Value, settings.MinSufixLength.Value, settings.MaxSufixLength.Value, settings.SiteIndex, breakOnCollision, callback);
				foreach (var s2 in res)
				{
					Console.WriteLine("Best size {0}-{1}", s2.PrefixLength, s2.PostfixLength);
				}
			}
			else
			{
				var wnd = SampleWindow.New(settings.PrefixLength.Value, settings.SufixLength.Value);
				var s2 = SampleCollector.CollectSamples(rs.sequence, wnd, breakOnCollision, settings.SiteIndex);
				if (s2.CollisionsWithNegativeSet.Count > 0)
				{
					Console.WriteLine("Collisions against NEGATIVE SAMPLE set");
					foreach (var item in s2.CollisionsWithNegativeSet.OrderBy(x => x.SampleOriginal.Origin.accession))
					{
						DisplayCollision(item);
					}
				}
				if (s2.CollisionsWithPositiveSet.Count > 0)
				{
					Console.WriteLine("Collisions against POSITIVE SAMPLE set");
					foreach (var item in s2.CollisionsWithPositiveSet.OrderBy(x => x.SampleOriginal.Origin.accession))
					{
						DisplayCollision(item);
					}
				}
				SampleCollector.ResolveCollisions(s2.CollisionsWithNegativeSet, s2.CollisionsWithPositiveSet, s2.PositiveSamples, s2.NegativeSamples);
				var dataset = SampleCollector.CreateDataset(s2);
				dataset.SaveToFile(settings.OutputFile);
			}
		}

		

		/// <summary>
		/// Muestra información por consola sobre una colision
		/// </summary>		
		private static void DisplayCollision(Collision item)
		{
			Console.WriteLine("{0}:{1} - {2}:{3} | size: {4} | offset: {5}",
						item.SampleOriginal.Origin.accession, item.SampleOriginal.Site,
						item.SampleColliding.Origin.accession, item.SampleColliding.Site,
						item.Size, item.SampleOriginal.Site - item.SampleColliding.Site);
		}

		/// <summary>
		/// Construye la estructura de datos para ejecutar lo que desea el usuario
		/// </summary>
		private static void ParseArguments(string[] args, out ExecSettings settings)
		{
			if (args.Length < 2)
			{
				ShowUsage();
				Environment.Exit(255);
			}

			settings = new ExecSettings();
			settings.InputFile = args[0];
			var tags = args.Skip(1);

			const string MODE_PARAM = "--mode=";
			const string PREFIX_PARAM = "-p=";
			const string SUFIX_PARAM = "-s=";
			const string MINPREFIX_PARAM = "-minp=";
			const string MINSUFIX_PARAM = "-mins=";
			const string MAXPREFIX_PARAM = "-maxp=";
			const string MAXSUFIX_PARAM = "-maxs=";
			const string SITE_PARAM = "--site=";
			const string MCO_PARAM = "-mco=";
			const string MCON_PARAM = "-mcon=";
			const string MCOP_PARAM = "-mcop=";
			const string OUTPUT_PARAM = "-o=";
			const string BREAKONCOLLISION_PARAM = "-b";

			foreach (var tag in tags)
			{
				if (tag.StartsWith(MODE_PARAM))
				{
					var mode = tag.Substring(MODE_PARAM.Length);
					if (mode == "collect") settings.Mode = Mode.Collect;
					else if (mode == "bestwindow") settings.Mode = Mode.FindBestWindow;
					else if (mode == "minsubstring") settings.Mode = Mode.MinSubstringLength;
					else
					{
						Console.WriteLine("Invalid conversion mode \"{0}\"", mode);
						Environment.Exit(255);
					}
				}
				else if (tag.StartsWith(PREFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(PREFIX_PARAM.Length));
					settings.PrefixLength = value;
				}
				else if (tag.StartsWith(SUFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(SUFIX_PARAM.Length));
					settings.SufixLength = value;
				}
				else if (tag.StartsWith(MINPREFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(MINPREFIX_PARAM.Length));
					settings.MinPrefixLength = value;
				}
				else if (tag.StartsWith(MINSUFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(MINSUFIX_PARAM.Length));
					settings.MinSufixLength = value;
				}
				else if (tag.StartsWith(MAXPREFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(MAXPREFIX_PARAM.Length));
					settings.MaxPrefixLength = value;
				}
				else if (tag.StartsWith(MAXSUFIX_PARAM))
				{
					var value = int.Parse(tag.Substring(MAXSUFIX_PARAM.Length));
					settings.MaxSufixLength = value;
				}
				else if (tag.StartsWith(SITE_PARAM))
				{
					var value = int.Parse(tag.Substring(SITE_PARAM.Length));
					settings.SiteIndex = value;
				}
				else if (tag.StartsWith(MCO_PARAM))
				{
					var value = int.Parse(tag.Substring(MCO_PARAM.Length));
					settings.MaxCollidingOffset = value;
				}
				else if (tag.StartsWith(MCON_PARAM))
				{
					var value = int.Parse(tag.Substring(MCON_PARAM.Length));
					settings.MaxNegativeCollidingOffset = value;
				}
				else if (tag.StartsWith(MCOP_PARAM))
				{
					var value = int.Parse(tag.Substring(MCOP_PARAM.Length));
					settings.MaxPositiveCollidingOffset = value;
				}
				else if (tag.StartsWith(OUTPUT_PARAM))
				{
					var value = tag.Substring(OUTPUT_PARAM.Length);
					settings.OutputFile = value;
				}
				else if (tag == BREAKONCOLLISION_PARAM)
				{
					settings.BreakOnCollision = true;
				}
				else
				{
					Console.WriteLine("Invalid tag \"{0}\"", tag);
					Environment.Exit(255);
				}
			}
		}

		/// <summary>
		/// Muestra como se usa el programa por la consola
		/// </summary>
		private static void ShowUsage()
		{
			var help =
@"seqsampler Parse SPMatch XML file and build a dataset
Build the positive and negative sample set.

	seqsampler input --mode=MODE args 

 MODE:
	bestwindow     Find the shortest prefix-sufix lengths pair for leading to non colliding samples
	minsubstring   Calc length of the shortest non repeating substring
	collect        Build the negative and positive sample set

 args:
	-p=LEN        Prefix sampling window length
	-minp=LEN     Minimum Prefix sampling window length (best window)
	-maxp=LEN     Maximum Prefix sampling window length (best window)
	
	-s=LEN					Sufix sampling window length
	-mins=LEN			Minimum Sufix sampling window length (best window)
	-maxs=LEN			Maximum Sufix sampling window length (best window)
	
	--site=INDEX  Zero-based site index
	-mco=MAX      Maximum inclusive offset between colliding samples (default: Off)
	-mcop=MAX     Maximum inclusive offset between p-colliding samples (default: Off)
	-mcon=MAX     Maximum inclusive offset between n-colliding samples (default: Off)
	-b							Break on collision (If used with '-mco','-mcop' or '-mcon' then '-b' will be ignored)
	-o=FILE       Output file path
	
";
			Console.WriteLine(help);
		}
	}
}
